﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace DNX.IoCnoC
{
    public interface IDerivetatedTypes
    {
        Type[] GetTypes(Type type);
    }

    public class DerivetatedTypes : IDerivetatedTypes
    {
        private readonly Assembly[] assemblies;

        public DerivetatedTypes(IEnumerable<string> assemblyNames)
        {
            assemblies = assemblyNames.Select(Assembly.Load).ToArray();
        }

        public Type[] GetTypes(Type type)
        {
            var typesResult = new List<Type>();

            if (type.IsGenericType)
                return GetGenericTypes(type, assemblies, typesResult).ToArray();

            foreach (var assembly in assemblies)
            {
                    typesResult.AddRange(assembly
                               .GetTypes()
                               .Where(i => i.IsClass && type.IsAssignableFrom(i)));    
            }
            return typesResult.ToArray();
        }

        private static List<Type> GetGenericTypes(Type type, Assembly[] assemblies, List<Type> typesResult)
        {
            var generic = type.GetGenericTypeDefinition();

            foreach (var assembly in assemblies)
            {
                typesResult.AddRange(assembly
                    .GetTypes()
                    .Where(i => i.IsClass && (i.GetInterfaces().Any(item => item.GUID == generic.GUID))));
            }
            var typesDefinedResult = new List<Type>();
            foreach (var item in typesResult)
            {
                if (item.GenericTypeArguments.Any())
                {
                    typesDefinedResult.Add(item);
                }
                else
                {
                    var genericTypes = new List<Type>();
                    foreach (var i in type.GenericTypeArguments)
                    {
                        if (i.IsGenericType)
                           genericTypes.Add(GetGenericTypes(i, assemblies, new List<Type>()).First());
                        else
                            genericTypes.Add(i);
                    }
                    typesDefinedResult.Add(item.MakeGenericType(genericTypes.ToArray()));
                }
            }
            return typesDefinedResult;
        }
    }
}                else
                            genericTypes.Add(i);
                    }
                    typesDefinedResult.Add(item.MakeGenericType(genericTypes.ToArray()));
                }
            }
            return typesDefinedResult;
        }
    }
}